import * as step from '@flow-step/step-toolkit'
import process from 'process'
import {getParams, IParams} from './params'
import {installKubectl} from './install'
import {getKubernetesCertificate} from './certificate'
import {
    checkContainerConfig, copyToTargetBin,
    execSyncReturnStdout,
    prepareKubectlArgs
} from './util'
import path from 'path'
import {execSync} from 'node:child_process'
import {polyglot} from './i18n'

const SEP = path.sep
const IS_LINUX = process.platform === 'linux'
const IS_X64 = process.arch === 'x64'

function printParams(params: IParams) {
    step.infoV1('')
    step.infoV1('====================================')
    step.infoV1(`    CLUSTER TYPE ${params.clusterType}    `)
    step.infoV1('====================================')
    step.infoV1('')
    step.infoV1('[USER] KUBECTL_VERSION=' + params.kubectlVersion)
    step.infoV1('[USER] CLUSTER_TYPE=' + params.clusterType)
    step.infoV1('[USER] NAMESPACE=' + params.namespace)
    step.infoV1('[USER] INSECURE_SKIP_TLS_VERIFY=' + params.insecureSkipTlsVerify)
    step.infoV1('[USER] KIND=' + params.kind)
    step.infoV1('[USER] NAME=' + params.name)
    step.infoV1('[USER] CONTAINER_NAME=' + params.containerName)
}

function validImageParam(params: IParams) {
    const output2envBin = copyToTargetBin(__dirname + SEP + 'output2env', path.join(params.workSpace, 'output2env'))
    const output2envBinArgs = [`-data='${params.image}'`]
    step.debug(`exec command: ${output2envBin} ${output2envBinArgs.join(' ')}`)
    const image = execSync(`${output2envBin} ${output2envBinArgs.join(' ')}`).toString().trim()
    if (image === '') {
        let errorMsg = polyglot.t("unable_to_find_the_image_to_publish")
        step.addOutput(`ERR_INFO: ${errorMsg}`)
        throw new Error(errorMsg)
    }
    return image
}

async function checkWorkloadStatus(params: IParams, kubectlPath: string, kubectlArgs: string[]) {
    const checkIntervalSeconds = 15
    const checkCount = params.rolloutTimeoutSeconds / checkIntervalSeconds
    step.infoV1(`${polyglot.t("checking_the_publication_results", { rolloutTimeoutSeconds: params.rolloutTimeoutSeconds, checkCount })}`)
    let exitCode = 1
    for (let i = 0; i < checkCount; i++) {
        // 每十次请求刷新一次 kubernetesCertificate
        if (i > 0 && i % 10 === 0) {
            step.debug(polyglot.t("check_retrieve", { i }))
            kubectlArgs = prepareKubectlArgs(params, await getKubernetesCertificate(), `.kube_${Date.now()}`)
        }

        const getWorkloadJsonCommand = `${kubectlPath} ${kubectlArgs.join(' ')} get ${params.kind} ${params.name} -o json`
        const output = execSyncReturnStdout(getWorkloadJsonCommand)

        // hack: avoid Error from server (InternalError): an error on the server ("unknown") has prevented the request from succeeding
        if (output === '') {
            step.debug(polyglot.t("check_retrieve_refresh_Kubernetes", { i }))
            kubectlArgs = prepareKubectlArgs(params, await getKubernetesCertificate(), `.kube_${Date.now()}`)
            continue
        }

        const status = JSON.parse(output).status
        const replicas = status?.replicas || 0
        const readyReplicas = status?.readyReplicas || 0
        const availableReplicas = status?.availableReplicas || 0
        step.infoV1(`check_[${i}] replicas ${replicas} readyReplicas ${readyReplicas} availableReplicas ${availableReplicas}`)

        if (replicas === readyReplicas || replicas === availableReplicas) {
            exitCode = 0
            break
        }

        await new Promise(resolve => setTimeout(resolve, checkIntervalSeconds * 1000)) // sleep for 10 seconds
    }
    return exitCode
}

async function execute(params: IParams): Promise<void> {
    // install kubectl binary first
    const kubectlPath = await installKubectl(params.kubectlVersion)

    const kubernetesCertificate = await getKubernetesCertificate()
    params.clusterType = kubernetesCertificate.type
    printParams(params)

    // output2env -data=${IMAGE}
    const image = validImageParam(params)

    step.infoV1('')
    step.infoV1(polyglot.t("kubernetes_cluster_basic_information"))
    step.infoV1('')

    let kubectlArgs = prepareKubectlArgs(params, kubernetesCertificate, `.kube_${Date.now()}`)
    step.debug(`exec command: ${kubectlPath} ${kubectlArgs.join(' ')} version`)
    let resultCode = await step.exec.call(kubectlPath, kubectlArgs.concat(['version']), {
        listener: {
            stdoutLine: (line: string) => step.infoV1(line),
            stderrLine: (line: string) => step.infoV1(line)
        }
    })
    if (resultCode !== 0) {
        step.errorV1(`${kubectlPath} version execute failed with exit code: ${resultCode}`)
        let errorMsg = polyglot.t("failed_to_connect_to_cluster")
        step.addOutput(`ERR_INFO: ${errorMsg}`)
        throw new Error(errorMsg)
    }

    step.debug(`exec command: ${kubectlPath} ${kubectlArgs.join(' ')} get ${params.kind} ${params.name}`)
    resultCode = await step.exec.call(kubectlPath, kubectlArgs.concat(['get', params.kind, params.name]), {
        listener: {
            stdoutLine: (line: string) => step.infoV1(line),
            stderrLine: (line: string) => step.infoV1(line)
        }
    })
    if (resultCode !== 0) {
        let errorMsg = polyglot.t("target_resources_published_do_not_exist", {
            kind: params.kind,
            name: params.name,
            namespace: params.namespace
        })
        step.errorV1(errorMsg)
        step.addOutput(`ERR_INFO: ${errorMsg}`)
        throw new Error(errorMsg)
    }

    step.debug(`exec command: ${kubectlPath} ${kubectlArgs.join(' ')} get ${params.kind} ${params.name} -o json`)
    const getWorkloadJsonCommand = `${kubectlPath} ${kubectlArgs.join(' ')} get ${params.kind} ${params.name} -o json`
    const output = execSyncReturnStdout(getWorkloadJsonCommand)
    // hack rewrite containerName when single container
    checkContainerConfig(params, output)

    step.infoV1(polyglot.t("upcoming_release_to_online", { image }))
    step.infoV1(`${kubectlPath} set image ${params.kind}/${params.name} ${params.containerName}=${image} --dry-run=true`)
    const dryRunOutput = execSyncReturnStdout(`${kubectlPath} ${kubectlArgs.join(' ')} set image ${params.kind}/${params.name} ${params.containerName}=${image} --dry-run=true`)

    // 理论上前序 ['get', params.kind, params.name] 已经拦截掉这种错误情况
    if (dryRunOutput.includes('NotFound')) {
        step.infoV1(dryRunOutput)
        let errorMsg = polyglot.t("application_does_not_exist", { kind: params.kind, name: params.name, namespace: params.namespace })
        step.errorV1(errorMsg)
        step.addOutput(`ERR_INFO: ${errorMsg}`)
        throw new Error(errorMsg)
    }

    const resources = dryRunOutput.split('\n').map(line => line.split(' ')[0])
    step.infoV1(resources.join('\n'))

    step.infoV1('')
    step.infoV1(`[INFO] ${polyglot.t("deploy_changes_to_kubernetes_cluster")}`)

    step.debug(`exec command: ${kubectlPath} ${kubectlArgs.join(' ')} set image ${params.kind}/${params.name} ${params.containerName}=${image}`)
    resultCode = await step.exec.call(kubectlPath, kubectlArgs.concat(['set', 'image', `${params.kind}/${params.name}`, `${params.containerName}=${image}`]), {
        listener: {
            stdoutLine: (line: string) => step.infoV1(line),
            stderrLine: (line: string) => step.infoV1(line)
        }
    })
    if (resultCode !== 0) {
        let errorMsg = polyglot.t("resource_container_image_update_failed", {
            kind: params.kind,
            name: params.name,
            containerName: params.containerName
        })
        step.errorV1(errorMsg)
        step.addOutput(`ERR_INFO: ${errorMsg}`)
        throw new Error(errorMsg)
    }

    step.infoV1('')
    step.infoV1(`[INFO] ${polyglot.t("print_the_current_task_execution_result")}`)

    for (const resource of resources) {
        if (resource.trim()) {
            step.infoV1(resource)
            step.infoV1('')
            step.infoV1(execSyncReturnStdout(`${kubectlPath} ${kubectlArgs.join(' ')} get ${resource}`))
        }
    }

    // 增加发布检测的逻辑
    const exitCode = await checkWorkloadStatus(params, kubectlPath, kubectlArgs)

    step.addOutput(`RERUNABLE=true`)
    step.addOutput(`RELEASE_INFO=[{ "deploySystem": "kubernetes", "packageLabel": "${image}"}]`)

    if (exitCode != 0) {
        step.errorV1(polyglot.t("publication_detection_timeout"))
        process.exit(exitCode)
    }
}

async function runStep(): Promise<void> {
    if (!IS_LINUX || !IS_X64) {
        // output2env golang binary support linux/amd64 only now
        throw new Error('KubectlSetImage only supports Linux/amd64 systems now')
    }

    const params = getParams()
    step.info(`PIPELINE_ID=${params.pipelineID}`)
    step.info(`PIPELINE_NAME=${params.pipelineName}`)
    step.info(`BUILD_NUMBER=${params.buildNumber}`)
    step.info(`WORK_SPACE=${params.workSpace}`)
    step.info(`PROJECT_DIR=${params.projectDir}`)
    step.info(`BUILD_JOB_ID=${params.buildJobID}`)

    await execute(params)
}

runStep()
    .then(function() {
        step.success('run step successfully!')
    })
    .catch(function(err: Error) {
        step.error(err.message)
        process.exit(-1)
    })
